package ch.nostromo.tiffanys.game.board;

import java.util.StringTokenizer;

import ch.nostromo.lib.util.NosTools;
import ch.nostromo.tiffanys.game.move.Move;
import ch.nostromo.tiffanys.game.move.MoveInput;

public class Board implements Cloneable {

  public static final int BOARD_VOID = 1;

  public static final int BOARD_LEGAL = 2;

  public static final int OWNER_WHITE = 4;

  public static final int OWNER_BLACK = 8;

  public static final int PIECE_KING = 16;

  public static final int PIECE_QUEEN = 32;

  public static final int PIECE_BISHOP = 64;

  public static final int PIECE_KNIGHT = 128;

  public static final int PIECE_ROOK = 256;

  public static final int PIECE_PAWN = 512;

  public static final int EMPTY_FIELD = 1024;

  public static final int MOVED = 2048;

  public static final int IDX_ENPASSANT_FIELD = 120;

  /** Board array (cloned) */
  private int[] boardArray = new int[121];

  public Board() {
    boardArray = getStartingBoard();
  }

  public Board(int[] board) {
    this.boardArray = board;
  }

  public Board(String fen) {
    boardArray = getEmptyBoard();
    flushBoard();
    StringTokenizer st = new StringTokenizer(fen, "/");
    int currLineIdx = 9;
    while (st.hasMoreTokens()) {
      String line = st.nextToken();
      int fieldIdx = 1;
      for (int i = 0; i < line.length(); i++) {
        char c = line.charAt(i);
        if (Character.isDigit(c)) {
          fieldIdx += Integer.parseInt(line.substring(i, i + 1));
        } else {
          if (c == 'K') {
            placePiece(currLineIdx * 10 + fieldIdx, OWNER_WHITE, PIECE_KING);
          } else if (c == 'Q') {
            placePiece(currLineIdx * 10 + fieldIdx, OWNER_WHITE, PIECE_QUEEN);
          } else if (c == 'R') {
            placePiece(currLineIdx * 10 + fieldIdx, OWNER_WHITE, PIECE_ROOK);
          } else if (c == 'B') {
            placePiece(currLineIdx * 10 + fieldIdx, OWNER_WHITE, PIECE_BISHOP);
          } else if (c == 'N') {
            placePiece(currLineIdx * 10 + fieldIdx, OWNER_WHITE, PIECE_KNIGHT);
          } else if (c == 'P') {
            if (currLineIdx == 3) {
              placePiece(currLineIdx * 10 + fieldIdx, OWNER_WHITE, PIECE_PAWN);
            } else {
              placePiece(currLineIdx * 10 + fieldIdx, OWNER_WHITE + MOVED, PIECE_PAWN);
            }
          } else if (c == 'k') {
            placePiece(currLineIdx * 10 + fieldIdx, OWNER_BLACK, PIECE_KING);
          } else if (c == 'q') {
            placePiece(currLineIdx * 10 + fieldIdx, OWNER_BLACK, PIECE_QUEEN);
          } else if (c == 'r') {
            placePiece(currLineIdx * 10 + fieldIdx, OWNER_BLACK, PIECE_ROOK);
          } else if (c == 'b') {
            placePiece(currLineIdx * 10 + fieldIdx, OWNER_BLACK, PIECE_BISHOP);
          } else if (c == 'n') {
            placePiece(currLineIdx * 10 + fieldIdx, OWNER_BLACK, PIECE_KNIGHT);
          } else if (c == 'p') {
            if (currLineIdx == 8) {
              placePiece(currLineIdx * 10 + fieldIdx, OWNER_BLACK, PIECE_PAWN);
            } else {
              placePiece(currLineIdx * 10 + fieldIdx, OWNER_BLACK + MOVED, PIECE_PAWN);
            }
          }
          fieldIdx++;
        }
      }
      currLineIdx--;

    }

  }

  public String getFENPosition() {
    String position = "";
    for (int n = 9; n >= 2; n--) {
      String line = "";
      int sinceLast = 0;
      for (int i = n * 10 + 1; i <= n * 10 + 8; i++) {
        if (isEmptyField(i)) {
          sinceLast++;
        } else {
          if (sinceLast > 0) {
            line += sinceLast;
            sinceLast = 0;
          }
          int piece = getPieceCode(i);
          if (isOwnerOfField(i, OWNER_WHITE)) {
            line += BoardTools.getPieceDescriptionByPiceCode(piece).toUpperCase();
          } else {
            line += BoardTools.getPieceDescriptionByPiceCode(piece).toLowerCase();
          }
        }
      }
      if (sinceLast > 0) {
        line += sinceLast;
      }
      position += line;
      if (n > 2) {
        position += "/";
      }
    }

    return position;
  }

  public String getFENCastling() {

    boolean Q = false;
    boolean K = false;
    boolean q = false;
    boolean k = false;

    if (!isMovedField(21) && !isMovedField(25) && getPieceCode(21) == Board.PIECE_ROOK && getPieceCode(25) == Board.PIECE_KING && isOwnerOfField(21, OWNER_WHITE) && isOwnerOfField(25, OWNER_WHITE)) {
      Q = true;
    }
    if (!isMovedField(28) && !isMovedField(25) && getPieceCode(28) == Board.PIECE_ROOK && getPieceCode(25) == Board.PIECE_KING && isOwnerOfField(28, OWNER_WHITE) && isOwnerOfField(25, OWNER_WHITE)) {
      K = true;
    }
    if (!isMovedField(91) && !isMovedField(95) && getPieceCode(91) == Board.PIECE_ROOK && getPieceCode(95) == Board.PIECE_KING && isOwnerOfField(91, OWNER_BLACK) && isOwnerOfField(95, OWNER_BLACK)) {
      q = true;
    }
    if (!isMovedField(98) && !isMovedField(95) && getPieceCode(98) == Board.PIECE_ROOK && getPieceCode(95) == Board.PIECE_KING && isOwnerOfField(98, OWNER_BLACK) && isOwnerOfField(95, OWNER_BLACK)) {
      k = true;
    }

    if (!Q && !K && !q && !k) {
      return "-";
    } else {
      String result = "";
      if (K) {
        result += "K";
      }
      if (Q) {
        result += "Q";
      }
      if (k) {
        result += "k";
      }
      if (q) {
        result += "q";
      }
      return result;
    }

  }

  public String getFENEnPassant() {
    if (boardArray[IDX_ENPASSANT_FIELD] > 0) {
      return BoardTools.fieldToCoord(boardArray[IDX_ENPASSANT_FIELD]);
    } else {
      return "-";
    }
  }

  public int[] getBoardArray() {
    return boardArray;
  }


  public void setBoardArray(int[] boardArray) {
    for (int i = 0; i < boardArray.length; i++) {
      this.boardArray[i] = boardArray[i];
    }
  }

  
  private int[] getEmptyBoard() {
    int[] board = new int[121];

    // First, all fields are "legal"
    for (int i = 0; i < 120; i++) {
      board[i] = BOARD_LEGAL;
    }
    // Overwrite some fields as "void"
    for (int i = 0; i <= 19; i++) {
      board[i] = BOARD_VOID;
    }
    for (int i = 20; i <= 90; i += 10) {
      board[i] = BOARD_VOID;
    }
    for (int i = 29; i <= 99; i += 10) {
      board[i] = BOARD_VOID;
    }
    for (int i = 100; i <= 119; i++) {
      board[i] = BOARD_VOID;
    }

    board[IDX_ENPASSANT_FIELD] = -1;

    return board;
  }

  private int[] getStartingBoard() {
    int[] board = getEmptyBoard();

    // White pieces
    board[21] += OWNER_WHITE + PIECE_ROOK;
    board[28] += OWNER_WHITE + PIECE_ROOK;
    board[22] += OWNER_WHITE + PIECE_KNIGHT;
    board[27] += OWNER_WHITE + PIECE_KNIGHT;
    board[23] += OWNER_WHITE + PIECE_BISHOP;
    board[26] += OWNER_WHITE + PIECE_BISHOP;
    board[24] += OWNER_WHITE + PIECE_QUEEN;
    board[25] += OWNER_WHITE + PIECE_KING;

    for (int i = 31; i <= 38; i++) {
      board[i] += OWNER_WHITE + PIECE_PAWN;
    }

    // White pieces
    board[91] += OWNER_BLACK + PIECE_ROOK;
    board[98] += OWNER_BLACK + PIECE_ROOK;
    board[92] += OWNER_BLACK + PIECE_KNIGHT;
    board[97] += OWNER_BLACK + PIECE_KNIGHT;
    board[93] += OWNER_BLACK + PIECE_BISHOP;
    board[96] += OWNER_BLACK + PIECE_BISHOP;
    board[94] += OWNER_BLACK + PIECE_QUEEN;
    board[95] += OWNER_BLACK + PIECE_KING;

    for (int i = 81; i <= 88; i++) {
      board[i] += OWNER_BLACK + PIECE_PAWN;
    }

    for (int n = 2; n <= 9; n++) {
      for (int i = n * 10 + 1; i <= n * 10 + 8; i++) {
        if (!NosTools.checkMask(board[i], OWNER_WHITE) && !NosTools.checkMask(board[i], OWNER_BLACK))
          board[i] = EMPTY_FIELD + BOARD_LEGAL;
      }
    }

    return board;
  }

  public void flushBoard() {
    boardArray = getEmptyBoard();
    for (int n = 2; n <= 9; n++) {
      for (int i = n * 10 + 1; i <= n * 10 + 8; i++) {
        if (!NosTools.checkMask(boardArray[i], OWNER_WHITE) && !NosTools.checkMask(boardArray[i], OWNER_BLACK))
          boardArray[i] = EMPTY_FIELD + BOARD_LEGAL;
      }
    }
  }

  public void placePiece(String coord, int owner, int piece) throws BoardException {
    int fieldIdx = BoardTools.coordToField(coord);
    placePiece(fieldIdx, owner, piece);
  }

  public void placePiece(int fieldIdx, int owner, int piece) {
    boardArray[fieldIdx] = BOARD_LEGAL + owner + piece;
  }

  public void clearField(String coord) throws BoardException {
    int field = BoardTools.coordToField(coord);
    boardArray[field] = BOARD_LEGAL + EMPTY_FIELD;
  }

  @Override
  public Board clone() throws CloneNotSupportedException {
    Board result = (Board) super.clone();
    
    if (boardArray != null) {
      result.boardArray = boardArray.clone();
    }
    
    return result;
  }

  public int getMaterialCount(int owner) {
    int result = 0;
    for (int n = 2; n <= 9; n++) {
      for (int i = n * 10 + 1; i <= n * 10 + 8; i++) {
        if (NosTools.checkMask(boardArray[i], owner)) {
          result++;
        }
      }
    }
    return result;
  }

  public int getTotalMaterialCount() {
    return getMaterialCount(Board.OWNER_BLACK) + getMaterialCount(Board.OWNER_WHITE);
  }

  public int getKingField(int owner) {
    for (int n = 2; n <= 9; n++) {
      for (int i = n * 10 + 1; i <= n * 10 + 8; i++) {
        if (NosTools.checkMask(boardArray[i], owner) && NosTools.checkMask(boardArray[i], PIECE_KING)) {
          return i;
        }
      }
    }
    return -1;
  }

  public static int invertOwner(int owner) throws BoardException {
    if (owner == Board.OWNER_WHITE) {
      return Board.OWNER_BLACK;
    } else if (owner == Board.OWNER_BLACK) {
      return Board.OWNER_WHITE;
    }

    throw new BoardException("Unknown owner: " + owner);
  }

  public boolean isValidMoveToField(int idx, int owner) throws BoardException {
    return (isEmptyField(idx) || isOpponentField(idx, owner));
  }

  public boolean isOpponentField(int idx, int owner)  throws BoardException{
    return isOwnerOfField(idx, invertOwner(owner));
  }

  public boolean isVoidField(int idx) {
    return (NosTools.checkMask(boardArray[idx], Board.BOARD_VOID));
  }

  public boolean isMovedField(int idx) {
    return (NosTools.checkMask(boardArray[idx], Board.MOVED));
  }
  
  public void setMoveField(int idx) {
    if (!isMovedField(idx)) {
      boardArray[idx] += Board.MOVED;
    }
  }

  public boolean isEmptyField(int idx) {
    return (NosTools.checkMask(boardArray[idx], Board.EMPTY_FIELD));
  }

  public boolean isOwnerOfField(int idx, int owner) {
    return NosTools.checkMask(boardArray[idx], owner);
  }

  public int getPieceOwner(int idx) {

    if (NosTools.checkMask(boardArray[idx], OWNER_WHITE)) {
      return OWNER_WHITE;
    } else if (NosTools.checkMask(boardArray[idx], OWNER_BLACK)) {
      return OWNER_BLACK;
    }

    throw new IllegalArgumentException("Unknown Owner: " + idx);
  }

  public int getPieceCode(int idx) {

    if (NosTools.checkMask(boardArray[idx], Board.PIECE_KING)) {
      return Board.PIECE_KING;
    } else if (NosTools.checkMask(boardArray[idx], Board.PIECE_QUEEN)) {
      return Board.PIECE_QUEEN;
    } else if (NosTools.checkMask(boardArray[idx], Board.PIECE_BISHOP)) {
      return Board.PIECE_BISHOP;
    } else if (NosTools.checkMask(boardArray[idx], Board.PIECE_KNIGHT)) {
      return Board.PIECE_KNIGHT;
    } else if (NosTools.checkMask(boardArray[idx], Board.PIECE_ROOK)) {
      return Board.PIECE_ROOK;
    } else if (NosTools.checkMask(boardArray[idx], Board.PIECE_PAWN)) {
      return Board.PIECE_PAWN;
    }

    return -1;
  }

  private final int getInitialShortRookField(int owner) {
    if (owner == Board.OWNER_WHITE) {
      return 28;
    } else {
      return 98;
    }
  }

  private final int getInitialLongRookField(int owner) {
    if (owner == Board.OWNER_WHITE) {
      return 21;
    } else {
      return 91;
    }
  }

  public void applyMove(MoveInput move) throws BoardException {

    
      int from = BoardTools.coordToField(move.getFrom());
      int to = BoardTools.coordToField(move.getTo());

      int movingOwner = getPieceOwner(from);
      int startingPiece = getPieceCode(from);

      boolean isShortCastling = false;
      boolean isLongCastling = false;

      if (startingPiece == PIECE_KING) {
        isShortCastling = ((from == 25 && to == 27) || (from == 95 && to == 97));
        isLongCastling = ((from == 25 && to == 23) || (from == 95 && to == 93));
      }

      int leavingEPField = -1;

      if (startingPiece == PIECE_PAWN) {
        if (movingOwner == Board.OWNER_WHITE) {
          if (to - from == 20) {
            leavingEPField = to - 10;
          }
        } else {
          if (to - from == -20) {
            leavingEPField = to + 10;
          }
        }
      }

      applyMove(from, to, movingOwner, startingPiece, isShortCastling, isLongCastling, move.isPromotion(), move.getPromotion(), leavingEPField);
    
  }

  public void applyMove(Move move) throws BoardException {

    int from = BoardTools.coordToField(move.getFrom());
    int to = BoardTools.coordToField(move.getTo());

    int movingOwner = getPieceOwner(from);

    int leavingEPField = -1;

    if (move.getStartingPiece() == PIECE_PAWN) {
      if (movingOwner == Board.OWNER_WHITE) {
        if (to - from == 20) {
          leavingEPField = to - 10;
        }
      } else {
        if (to - from == -20) {
          leavingEPField = to + 10;
        }
      }
    }

    applyMove(from, to, movingOwner, move.getStartingPiece(), move.isCastlingShort(), move.isCastlingLong(), move.isPromotion(), move.getPromotion(), leavingEPField);

  }

  private void applyMove(int from, int to, int movingOwner, int startingPiece, boolean isShortCastling, boolean isLongCastling, boolean isPromotion, int promotion, int leavingEPField) {

    // Castling first
    if (isShortCastling) {
      boardArray[from] = Board.BOARD_LEGAL + Board.EMPTY_FIELD;
      boardArray[to] = Board.BOARD_LEGAL + movingOwner + Board.PIECE_KING + Board.MOVED;
      boardArray[getInitialShortRookField(movingOwner)] = Board.BOARD_LEGAL + Board.EMPTY_FIELD;
      boardArray[getInitialShortRookField(movingOwner) - 2] = Board.BOARD_LEGAL + movingOwner + Board.PIECE_ROOK + Board.MOVED;
    } else if (isLongCastling) {
      boardArray[from] = Board.BOARD_LEGAL + Board.EMPTY_FIELD;
      boardArray[to] = Board.BOARD_LEGAL + movingOwner + Board.PIECE_KING + Board.MOVED;
      boardArray[getInitialLongRookField(movingOwner)] = Board.BOARD_LEGAL + Board.EMPTY_FIELD;
      boardArray[getInitialLongRookField(movingOwner) + 3] = Board.BOARD_LEGAL + movingOwner + Board.PIECE_ROOK + Board.MOVED;
    } else {

      // Remove piece
      boardArray[from] = Board.BOARD_LEGAL + Board.EMPTY_FIELD;

      // Move piece to new position
      if (isPromotion) {
        boardArray[to] = Board.BOARD_LEGAL + movingOwner + promotion + Board.MOVED;
      } else {
        boardArray[to] = Board.BOARD_LEGAL + movingOwner + startingPiece + Board.MOVED;
      }

      // Remove enpassant pawn if needed
      if (startingPiece == Board.PIECE_PAWN && to == boardArray[IDX_ENPASSANT_FIELD]) {
        if (movingOwner == OWNER_WHITE) {
          boardArray[to - 10] = Board.BOARD_LEGAL + Board.EMPTY_FIELD;
        } else {
          boardArray[to + 10] = Board.BOARD_LEGAL + Board.EMPTY_FIELD;
        }
      }

      // Set or remove enPassant field
      if (startingPiece == Board.PIECE_PAWN) {
        boardArray[Board.IDX_ENPASSANT_FIELD] = leavingEPField;
      } else {
        boardArray[Board.IDX_ENPASSANT_FIELD] = -1;
      }

    }

  }
  
  public void setFenCastlings(String castling) {

    if (castling.indexOf("Q") < 0) {
      if (getPieceCode(21) == Board.PIECE_ROOK && isOwnerOfField(21, OWNER_WHITE)) {
        this.setMoveField(21);
      }
    }
    if (castling.indexOf("K") < 0) {
      if (getPieceCode(28) == Board.PIECE_ROOK && isOwnerOfField(28, OWNER_WHITE)) {
        this.setMoveField(28);
      }
    }
    
    if (castling.indexOf("q") < 0) {
      if (getPieceCode(91) == Board.PIECE_ROOK && isOwnerOfField(91, OWNER_BLACK)) {
        this.setMoveField(91);
      }
    }
    if (castling.indexOf("k") < 0) {
      if (getPieceCode(98) == Board.PIECE_ROOK && isOwnerOfField(98, OWNER_BLACK)) {
        this.setMoveField(98);
      }
    }
  }
  
  public void setFenEpField(String fenInput) throws BoardException {
    if (!fenInput.equals("-")){
      int idX = BoardTools.coordToField(fenInput);
      this.boardArray[Board.IDX_ENPASSANT_FIELD] = idX;
    }
  }
  
}
